﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using DDD;
using System.Drawing;

using Vector2 = DDD.Vector2<float>;

namespace DevApp {
    public class Program7 : GameWindow {

        FrameBuffer fbuf;
        World wld1;
        World wld2;
        Graphics3D g3d;

        public Program7 ()
            : base (640, 480,
              new GraphicsMode (), "DDD: Deferred Shading", 0,
              DisplayDevice.Default, 3, 0,
              GraphicsContextFlags.ForwardCompatible) {
        }

        private Appearance CreateGeometryAppearance () {
            var vert = new VertexShader ("Shaders/phong.vert");
            var frag = new FragmentShader ("Shaders/geometry-rendering.frag");
            var sp = new ShaderProgram (vert, frag);

            var unifs1 = new UniformArray ();
            unifs1.Add ("ModelViewMatrix", UniformArray.CurrentNode, () => ((Node)null).LocalToCamera);
            unifs1.Add ("ModelViewNormalMatrix", UniformArray.CurrentNode, () => ((Node)null).LocalToCamera3x3);
            unifs1.Add ("ProjectionMatrix", UniformArray.CurrentActiveCamera, () => ((Camera)null).ProjectionMatrix);

            var unifs2 = new UniformArray ();
            unifs2.Add ("Diffuse", UniformArray.CurrentMaterial, () => ((PhongMaterial)null).Diffuse);

            var mat = new PhongMaterial ();
            mat.SetDiffuse (0.7f, 0.7f, 1.0f, 1);

            var app = new Appearance ();
            app.SetShaderProgram (sp);
            app.AddUniformArray (unifs1);
            app.AddUniformArray (unifs2);
            app.SetMaterial (mat);

            return app;
        }


        private Appearance CreateLightAppearance () {
            var windowSize = new Vector2 (Width, Height);
            var vert = new VertexShader ("Shaders/phong.vert");
            var frag = new FragmentShader ("Shaders/deffered-lighting.frag");
            var sp = new ShaderProgram (vert, frag);

            var unifs1 = new UniformArray ();
            unifs1.Add ("ModelViewMatrix", UniformArray.CurrentNode, () => ((Node)null).LocalToCamera);
            unifs1.Add ("ModelViewNormalMatrix", UniformArray.CurrentNode, () => ((Node)null).LocalToCamera3x3);
            unifs1.Add ("ProjectionMatrix", UniformArray.CurrentActiveCamera, () => ((Camera)null).ProjectionMatrix);

            var unifs2 = new UniformArray ();
            unifs2.Add ("LightPosition", UniformArray.CurrentNode, () => ((Node)null).CameraPosition);
            unifs2.Add ("LightColor", UniformArray.CurrentMaterial, () => ((LightMaterial)null).Color);
            unifs2.Add ("WindowSize", windowSize);
            unifs2.Add ("DiffTexture", 0);
            unifs2.Add ("PosTexture", 1);
            unifs2.Add ("NormTexture", 2);

            var mat = new LightMaterial ();
            mat.SetColor (0.7f, 0.7f, 0.7f);

            var cmp = new CompositeMode ();
            cmp.SetColorBlendFunction (ColorBlendFunction.Add);
            cmp.SetDepthTestEnable (false);

            var diffuseSampler = new Sampler (SamplerType.Texture2D);
            diffuseSampler.SetImage (fbuf.GetColorBuffer (0));

            var positionSampler = new Sampler (SamplerType.Texture2D);
            positionSampler.SetImage (fbuf.GetColorBuffer (1));

            var normalSampler = new Sampler (SamplerType.Texture2D);
            normalSampler.SetImage (fbuf.GetColorBuffer (2));

            var app = new Appearance ();
            app.SetShaderProgram (sp);
            app.AddUniformArray (unifs1);
            app.AddUniformArray (unifs2);
            app.SetMaterial (mat);
            app.SetComositeMode (cmp);
            app.AddSampler (diffuseSampler);
            app.AddSampler (positionSampler);
            app.AddSampler (normalSampler);

            return app;
        }

        private World CreateGeometryWorld () {

            var msh = MeshBuilder.CreateSquare (5, 4, 4, "v_Position", "v_Normal", null);
            msh.SetAppearance (CreateGeometryAppearance ());

            var mshNode = new Node ();
            mshNode.Attach (msh);

            var cam = new Camera ();
            cam.SetPerspective (45, Width / (float)Height, 1, 100);

            var camNode = new Node ();
            camNode.Attach (cam);
            camNode.Translate (0, 0, 10);

            var wld = new World ();
            wld.AddChild (mshNode);
            wld.AddChild (camNode);
            wld.SetActiveCamera (camNode);

            return wld;
        }

        private World CreateLightingWorld () {
            var lgh = MeshBuilder.CreateSphere (1, 5, 5, "v_Position", null);
            lgh.SetAppearance (CreateLightAppearance ());

            var lghNode = new Node ();
            lghNode.Attach (lgh);
            lghNode.Translate (0, 0, 2);

            var cam = new Camera ();
            cam.SetPerspective (45, Width / (float)Height, 1, 100);

            var camNode = new Node ();
            camNode.Attach (cam);
            camNode.Translate (0, 0, 10);

            var wld = new World ();
            wld.AddChild (lghNode);
            wld.AddChild (camNode);
            wld.SetActiveCamera (camNode);

            return wld;
        }

        private FrameBuffer CreateFrameBuffer () {
            var fbuf = new FrameBuffer (3, 1, 0);
            var diffuseTexture = new Image2D<byte> (DDD.PixelFormat.RGB8ui, Width, Height);
            var positionTexture = new Image2D<float> (DDD.PixelFormat.RGB8, Width, Height);
            var normalTexture = new Image2D<float> (DDD.PixelFormat.RGB8, Width, Height);
            var depthTexture = new Image2D<byte> (DDD.PixelFormat.Depth, Width, Height);
            fbuf.SetColorBuffer (0, diffuseTexture);
            fbuf.SetColorBuffer (1, positionTexture);
            fbuf.SetColorBuffer (2, normalTexture);
            fbuf.SetDepthBuffer (depthTexture);

            return fbuf;
        }

        protected override void OnLoad (System.EventArgs e) {

            fbuf = CreateFrameBuffer ();

            wld1 = CreateGeometryWorld ();

            wld2 = CreateLightingWorld ();


            g3d = Graphics3D.GetInstance ();


            // 1st pass : Geometry-Rendering
            g3d.RenderPass += delegate {
                g3d.SetWorld (wld1);
                g3d.SetFrameBuffer (fbuf);        // オフラインレンダリング用のフレームバッファーに変更
                g3d.SetClearColor (0.1f, 0.1f, 0.1f, 1);
                g3d.SetClearDepth (1);
                g3d.SetColorClearEnabled (true);
                g3d.SetDepthClearEnabled (true);
                g3d.SetViewort (0, 0, Width, Height);
            };


            // 2nd pass : Lighting
            g3d.RenderPass += delegate {
                g3d.SetWorld (wld2);
                g3d.SetFrameBuffer (null);         // デフォルトのフレームバッファーに戻す
                g3d.SetClearColor (0, 0, 0, 1);
                g3d.SetColorClearEnabled (true);
                g3d.SetDepthClearEnabled (false);
                g3d.SetViewort (0, 0, Width, Height);
            };


            // 未実装
            VSync = VSyncMode.On;
        }

        protected override void OnUnload (EventArgs e) {
            wld1 = null;
            wld2 = null;
            GC.Collect ();
            g3d.Release ();
        }

        protected override void OnUpdateFrame (FrameEventArgs e) {
            if (Keyboard[OpenTK.Input.Key.Escape])
                Exit ();
            if (Keyboard[OpenTK.Input.Key.Space]) {
                fbuf.GetColorBuffer (0).Capture ();
                fbuf.GetColorBuffer (0).Save ("ColorBuffer-0.png");
            }

        }


        protected override void OnRenderFrame (FrameEventArgs e) {
            g3d.Render ();

            SwapBuffers ();

        }

        /*
        [STAThread]
        public static void Main () {
            using (var example = new Program7 ()) {
                example.Run (30);
            }
        }
        */
    }
}
